/**
 * Copyright (C) 2011 Computer Science Department, Loei Rajabhat University
 * 
 * This class is porting from C/C++, Credit: http://codepedia.tistory.com/42
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package th.ac.lru.cs.ktd.keyboard;

public class KoreanVirtualKeyboardAutomata {

	private final int KEY_CODE_SPACE     = -1; // 띄어쓰기
	private final int KEY_CODE_ENTER 	 = -2; // 내려쓰기
	private final int KEY_CODE_BACKSPACE = -3; // 지우기	
	
	private final int BASE_CODE = 0xac00;
	private final int LIMIT_MIN = 0xac00;
	private final int LIMIT_MAX = 0xd7a3;
	
	private final String EMPTY_STRING = "";
	
	private MixWordStatus	  mStatus;          // 단어조합상태
	private int		  mPhonemez[] = new int[5]; // 음소[초,중,종,곁자음1,곁자음2]
	private char	  mCompleteWord;            // 완성글자

	public char		  ingWord;
	public String	  completeText = EMPTY_STRING;
	
	private final char[] SOUND_TABLE = {
	        /* 초성 19자 0 ~ 18 */
			'ㄱ','ㄲ', 'ㄴ', 'ㄷ', 'ㄸ',
			'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ',
			'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ',
			'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ',
			
			/* 중성 21자 19 ~ 39 */
			'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ',
			'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ',
			'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ',
			'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ',
			'ㅣ',
			
			/* 종성 28자 40 ~ 67 */
			' ' , 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ',
			'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 
			'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ',
			'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ',
			'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ',
			'ㅌ', 'ㅍ', 'ㅎ'
	};
	
	/** 
	 * 초성 합성 테이블 
	 * อักษรที่มีตัวอักษรซ้ำ 
	 * */
	private final int MIXED_CHO_CONSON[][] = {
			{ 0, 0,15}, // ㄱ,ㄱ,ㅋ
			{15, 0, 1}, // ㅋ,ㄱ,ㄲ
			{ 1, 0, 0}, // ㄲ,ㄱ,ㄱ

			{ 3, 3,16}, // ㄷ,ㄷ,ㅌ
			{16, 3, 4}, // ㅌ,ㄷ,ㄸ
			{ 4, 3, 3}, // ㄸ,ㄷ,ㄷ

			{ 7, 7,17}, // ㅂ,ㅂ,ㅍ
			{17, 7, 8}, // ㅍ,ㅂ,ㅃ
			{ 8, 7, 7}, // ㅃ,ㅂ,ㅂ

			{ 9, 9,10}, // ㅅ,ㅅ,ㅆ
			{10, 9, 9}, // ㅆ,ㅅ,ㅅ

			{12,12,14}, // ㅈ,ㅈ,ㅊ
			{14,12,13}, // ㅊ,ㅈ,ㅉ
			{13,12,12}  // ㅉ,ㅈ,ㅈ
	};
	
	/**
	 * 초성,중성 모음 합성 테이블 
	 * สระทั้งหมด 
	 * */
	private final int MIXED_VOWEL[][] =  {
			{19,19,21},	// ㅏ,ㅏ,ㅑ
			{21,19,19},	// ㅑ,ㅏ,ㅏ

			{19,39,20},	// ㅏ,ㅣ,ㅐ
			{21,39,22},	// ㅑ,ㅣ,ㅒ

			{23,23,25},	// ㅓ,ㅓ,ㅕ
			{25,23,23},	// ㅕ,ㅓ,ㅓ

			{23,39,24},	// ㅓ,ㅣ,ㅔ
			{25,39,26},	// ㅕ,ㅣ,ㅖ

			{27,27,31},	// ㅗ,ㅗ,ㅛ
			{31,27,27},	// ㅛ,ㅗ,ㅗ

			{27,19,28},	// ㅗ,ㅏ,ㅘ
			{28,39,29},	// ㅘ,ㅣ,ㅙ

			{27,39,30},	// ㅗ,ㅣ,ㅚ

			{32,32,36},	// ㅜ,ㅜ,ㅠ
			{36,32,32},	// ㅠ,ㅜ,ㅜ

			{32,23,33},	// ㅜ,ㅓ,ㅝ
			{33,39,34},	// ㅝ,ㅣ,ㅞ

			{32,39,35},	// ㅜ,ㅣ,ㅟ

			{39,39,37},	// ㅣ,ㅣ,ㅡ
			{37,39,38},	// ㅡ,ㅣ,ㅢ
			{38,39,39}	// ㅢ,ㅣ,ㅣ
	};

	/**
	 * 종성 합성 테이블 
	 * */
	private final int MIXED_JONG_CONSON[][] = {
			{41,41,64}, // ㄱ,ㄱ,ㅋ
			{64,41,42}, // ㅋ,ㄱ,ㄲ
			{42,41,41}, // ㄲ,ㄱ,ㄱ
	
			{41,59,43}, // ㄱ,ㅅ,ㄳ
	
			{44,62,45}, // ㄴ,ㅈ,ㄵ
			{44,67,46}, // ㄴ,ㅎ,ㄶ
	
			{47,47,65}, // ㄷ,ㄷ,ㅌ
			{65,47,47}, // ㅌ,ㄷ,ㄷ
	
			{48,41,49}, // ㄹ,ㄱ,ㄺ
			{48,56,50}, // ㄹ,ㅁ,ㄻ
	
			{48,57,51}, // ㄹ,ㅂ,ㄼ
			{51,57,54}, // ㄼ,ㅂ,ㄿ
	
			{48,59,52}, // ㄹ,ㅅ,ㄽ
			{48,47,53}, // ㄹ,ㄷ,ㄾ	
			{48,67,55}, // ㄹ,ㅎ,ㅀ
	
			{57,57,66}, // ㅂ,ㅂ,ㅍ
			{66,57,57}, // ㅍ,ㅂ,ㅂ
	
			{57,59,58}, // ㅂ,ㅅ,ㅄ
	
			{59,59,60}, // ㅅ,ㅅ,ㅆ
			{60,59,59}, // ㅆ,ㅅ,ㅅ
	
			{62,62,63}, // ㅈ,ㅈ,ㅊ
			{63,62,62}  // ㅊ,ㅈ,ㅈ
	};

	/** 
	 * 종성 분해 테이블 
	 * */
	private final int DIVIDE_JONG_CONSON[][] = {
			{41,41,42}, // ㄱ,ㄱ,ㄲ
			{41,59,43}, // ㄱ,ㅅ,ㄳ
			{44,62,45}, // ㄴ,ㅈ,ㄵ
			{44,67,46}, // ㄴ,ㅎ,ㄶ
			{48,41,49}, // ㄹ,ㄱ,ㄺ
			{48,56,50}, // ㄹ,ㅁ,ㄻ
			{48,57,51}, // ㄹ,ㅂ,ㄼ
			{48,66,54}, // ㄹ,ㅍ,ㄿ
			{48,59,52}, // ㄹ,ㅅ,ㄽ
			{48,65,53}, // ㄹ,ㅌ,ㄾ	
			{48,67,55}, // ㄹ,ㅎ,ㅀ
			{57,59,58}, // ㅂ,ㅅ,ㅄ
			{59,59,60}  // ㅅ,ㅅ,ㅆ
	};
	
	public void clear() {	
		mStatus			= MixWordStatus.HS_FIRST;
		completeText	= EMPTY_STRING;
		ingWord			= '\u0000';
		mCompleteWord	= '\u0000';
	}
	
	public char setKeyCode(int nKeyCode) {
		mCompleteWord = '\u0000';

		/* 특수키 입력 */
		if(nKeyCode < 0) {
			mStatus = MixWordStatus.HS_FIRST;

			// 띄어쓰기
			if (nKeyCode == KEY_CODE_SPACE) {
				if (ingWord != '\u0000') {
					completeText += ingWord;
				} else {
					completeText += " ";
				}
				ingWord = '\u0000';
				
			} else if(nKeyCode == KEY_CODE_ENTER) { // 내려쓰기
				if(ingWord != '\u0000')
					completeText += ingWord;
					completeText += "\r\n";
					ingWord = '\u0000';
					
				} else if(nKeyCode == KEY_CODE_BACKSPACE) { // 지우기
					if(ingWord == '\u0000') {
						if(completeText != null && completeText.length() > 0) {
							if (completeText.length() == (completeText.lastIndexOf("\n") + 1)) {
								completeText = completeText.substring(0, completeText.length() -2);
							} else {
								completeText = completeText.substring(0, completeText.length() -1);
							}
						}
						
					} else {
						mStatus = downGradeIngWordStatus(String.valueOf(ingWord));
					}
				}
				return mCompleteWord;
			}

			switch(mStatus) {
				case HS_FIRST:
					/* 초성 */
					mPhonemez[0]	= nKeyCode;
					ingWord			= SOUND_TABLE[mPhonemez[0]];
					mStatus			= nKeyCode > 18 ? MixWordStatus.HS_FIRST_C : MixWordStatus.HS_FIRST_V;
					break;
	
				case HS_FIRST_C:
					/* 모음 + 모음 */
					if(nKeyCode > 18) {	// 모음
						if(mixVowel(mPhonemez[0], nKeyCode, MixWordStatus.HS_FIRST_C) == false) {
							mCompleteWord = SOUND_TABLE[mPhonemez[0]];
							mPhonemez[0] = nKeyCode;
						}
					} else { // 자음
						mCompleteWord	= SOUND_TABLE[mPhonemez[0]];
						mPhonemez[0]	= nKeyCode;
						mStatus		= MixWordStatus.HS_FIRST_V;
					}
					break;
	
				case HS_FIRST_V:
					/* 자음 + 자음 */
					if(nKeyCode > 18) {	// 모음
						mPhonemez[1]	= nKeyCode;
						mStatus		= MixWordStatus.HS_MIDDLE_STATE;
						
					} else { // 자음
						if(!mixInitial(nKeyCode)) {
							mCompleteWord	= SOUND_TABLE[mPhonemez[0]];
							mPhonemez[0]	= nKeyCode;
							mStatus		= MixWordStatus.HS_FIRST_V;
						}
					}
					break;
	
				case HS_MIDDLE_STATE:
					/* 초성 + 모음 + 모음 */
					if(nKeyCode > 18) {
						if(mixVowel(mPhonemez[1], nKeyCode, MixWordStatus.HS_MIDDLE_STATE) == false) {
							mCompleteWord	= combineHangle(1);
							mPhonemez[0]	= nKeyCode;
							mStatus		= MixWordStatus.HS_FIRST_C;
						}
					} else {
						int jungCode = toFinal(nKeyCode);
	
						if(jungCode > 0) {
							mPhonemez[2]	= jungCode;
							mStatus		= MixWordStatus.HS_END_STATE;
						} else {
							mCompleteWord	= combineHangle(1);
							mPhonemez[0]	= nKeyCode;
							mStatus		= MixWordStatus.HS_FIRST;
						}
					}
					break;
	
				case HS_END:
					/* 초성 + 중성 + 종성 */
					if(nKeyCode > 18) {
						mCompleteWord	= combineHangle(1);
						mPhonemez[0]	= nKeyCode;
						mStatus		= MixWordStatus.HS_FIRST;
					} else {
						int jungCode = toFinal(nKeyCode);
						if(jungCode > 0) {
							mPhonemez[2]	= jungCode;
							mStatus		= MixWordStatus.HS_END_STATE;
						} else {
							mCompleteWord	= combineHangle(1);
							mPhonemez[0]	= nKeyCode;
							mStatus		= MixWordStatus.HS_FIRST;
						}  
					}
					break;
	
				case HS_END_STATE:
					/* 초성 + 중성 + 자음(종) + 자음(종) */
					if(nKeyCode > 18) {
						mCompleteWord = combineHangle(1);
						mPhonemez[0]	= toInitial(mPhonemez[2]);
						mPhonemez[1]	= nKeyCode;
						mStatus		= MixWordStatus.HS_MIDDLE_STATE;
					} else {
						int jungCode = toFinal(nKeyCode);
						if(jungCode > 0) {
							mStatus = MixWordStatus.HS_END_EXCEPTION;
							if(!mixFinal(jungCode)) {
								mCompleteWord	= combineHangle(2);
								mPhonemez[0]	= nKeyCode;
								mStatus		= MixWordStatus.HS_FIRST_V;
							}
						} else {
							mCompleteWord	= combineHangle(2);
							mPhonemez[0]	= nKeyCode;
							mStatus		= MixWordStatus.HS_FIRST_V;
						}  
					}
					break;
	
				case HS_END_EXCEPTION:
					/* 초성 + 중성 + 종성(곁자음) */
					if(nKeyCode > 18) {
						decomposeConsonant();
						mPhonemez[1]	= nKeyCode;
						mStatus		= MixWordStatus.HS_MIDDLE_STATE;
					} else {
						int jungCode = toFinal(nKeyCode);
						if(jungCode > 0) {
							mStatus = MixWordStatus.HS_END_EXCEPTION;
							if(!mixFinal(jungCode)) {
								mCompleteWord	= combineHangle(2);
								mPhonemez[0]	= nKeyCode;
								mStatus		= MixWordStatus.HS_FIRST_V;
							}
						} else {
							mCompleteWord	= combineHangle(2);
							mPhonemez[0]	= nKeyCode;
							mStatus		= MixWordStatus.HS_FIRST_V; 
						}
					}
					break;
			}

			/* 현재 보이는 글자상태 */
			combineIngWord(mStatus);

			/* 완성 문자열 만들기 */
			if(mCompleteWord != '\u0000') {
				completeText += mCompleteWord;
			}
			
		return mCompleteWord;
	}
	
	/**
	 *  초성으로 변환
	 */
	private int toInitial(int nKeyCode) {
		switch(nKeyCode) {
			case 41: return 0;	// ㄱ
			case 42: return 1;	// ㄲ
			case 44: return 2;	// ㄴ
			case 47: return 3;	// ㄷ
			case 48: return 5;	// ㄹ
			case 56: return 6;	// ㅁ
			case 57: return 7;	// ㅂ
			case 59: return 9;	// ㅅ
			case 60: return 10;	// ㅆ
			case 61: return 11;	// ㅇ
			case 62: return 12;	// ㅈ
			case 63: return 14;	// ㅊ
			case 64: return 15;	// ㅋ
			case 65: return 16;	// ㅌ
			case 66: return 17;	// ㅍ
			case 67: return 18;	// ㅎ
		}
		return -1;
	}

	/**
	 *  종성으로 변환
	 */
	private int toFinal(int nKeyCode) {
		switch(nKeyCode) {
			case 0: return 41;	// ㄱ
			case 1: return 42;	// ㄲ
			case 2: return 44;	// ㄴ
			case 3: return 47;	// ㄷ
			case 5: return 48;	// ㄹ
			case 6: return 56;	// ㅁ
			case 7: return 57;	// ㅂ
			case 9: return 59;	// ㅅ
			case 10: return 60;	// ㅆ
			case 11: return 61;	// ㅇ
			case 12: return 62;	// ㅈ
			case 14: return 63;	// ㅊ
			case 15: return 64;	// ㅋ
			case 16: return 65;	// ㅌ
			case 17: return 66;	// ㅍ
			case 18: return 67;	// ㅎ
		}
		return -1;
	}
	
	/**
	 *  자음분해
	 */
	private void decomposeConsonant() {
		int i = 0;
		if (mPhonemez[3] > 40 || mPhonemez[4] > 40) {
			do {
				if(DIVIDE_JONG_CONSON[i][2] == mPhonemez[2]) {
					mPhonemez[3] = DIVIDE_JONG_CONSON[i][0];
					mPhonemez[4] = DIVIDE_JONG_CONSON[i][1];
					mCompleteWord = combineHangle(3);
					mPhonemez[0] = toInitial(mPhonemez[4]);
					return;
				}
			}
			while(++i< 13);
		}

		mCompleteWord = combineHangle(1);
		mPhonemez[0] = toInitial(mPhonemez[2]);
	}	
	
	/**
	 *  초성합성
	 */
	private boolean mixInitial(int nKeyCode) {
		if (nKeyCode >= 19) {
			return false;
		}
		int i = 0;
		do {
			if(MIXED_CHO_CONSON[i][0] == mPhonemez[0] && MIXED_CHO_CONSON[i][1] == nKeyCode) {
				mPhonemez[0] = MIXED_CHO_CONSON[i][2];
				return true;
			}
		} while(++i < 14);
		return false;
	}	
	
	/**
	 *  종성합성
	 */
	private boolean mixFinal(int nKeyCode) {
		if(nKeyCode <= 40) {
			return false;
		}
		int i = 0;
		do {
			if (MIXED_JONG_CONSON[i][0] == mPhonemez[2] && MIXED_JONG_CONSON[i][1] == nKeyCode) {
				mPhonemez[3] = mPhonemez[2];
				mPhonemez[4] = nKeyCode;
				mPhonemez[2] = MIXED_JONG_CONSON[i][2];
				return true;
			}
		} while(++i < 22);
		return false;
	}

	/**
	 *  모음합성
	 */
	private boolean mixVowel(int currentCode, int inputCode, MixWordStatus status) {
		int i = 0;
		do {
			if(MIXED_VOWEL[i][0] == currentCode && MIXED_VOWEL[i][1] == inputCode) {
				currentCode = MIXED_VOWEL[i][2];
				switch (status) {
					case HS_FIRST_C:
						mPhonemez[0] = MIXED_VOWEL[i][2];
						break;
					case HS_MIDDLE_STATE:
						mPhonemez[1] = MIXED_VOWEL[i][2];
						break;
				}
				return true;
			}
		} while(++i< 21);
		return false;
	}
	
	/**
	 *  한글조합
	 */
	private char combineHangle(int status) {
		switch(status) {
			/* 초성พยัญชนะต้น + 중성สระที่ผสมในพยางค์ */
			case 1: 
				return combineHangle(mPhonemez[0], mPhonemez[1], 40);
	
			/* 초성พยัญชนะต้น + 중성สระที่ผสมในพยางค์ + 종성 ตัวสะกด */
			case 2: 
				return combineHangle(mPhonemez[0], mPhonemez[1], mPhonemez[2]);
			
			/* 초성พยัญชนะต้น  + 중성สระ  + 곁자음01 พยัญชนะผสม */
			case 3: 
				return combineHangle(mPhonemez[0], mPhonemez[1], mPhonemez[3]);
		}
		return ' ';
	}		
	
	private char combineHangle(int cho, int jung, int jong) {
		/* 초성 * 21 * 28 + (중성 - 19) * 28 + (종성 - 40) + BASE_CODE */
		return (char) ((char) BASE_CODE - 572 + jong + cho * 588 + jung * 28);
	}	
	
	private void combineIngWord(MixWordStatus status) {
		if (mStatus == MixWordStatus.HS_FIRST || mStatus == MixWordStatus.HS_FIRST_V || mStatus == MixWordStatus.HS_FIRST_C) {
			ingWord = SOUND_TABLE[mPhonemez[0]];
		}
		
		if (mStatus == MixWordStatus.HS_MIDDLE_STATE || mStatus == MixWordStatus.HS_END) {
			ingWord = combineHangle(1);
		}
		
		if (mStatus == MixWordStatus.HS_END_STATE || mStatus == MixWordStatus.HS_END_EXCEPTION) {
			ingWord = combineHangle(2);
		}
	}	
	
	private MixWordStatus downGradeIngWordStatus(String word) {
		int iWord = word.length();
		
		/* 초성만 있는 경우 */
		if(iWord < LIMIT_MIN || iWord > LIMIT_MAX) {
			ingWord = '\u0000';
			return MixWordStatus.HS_FIRST;
		}

		/* 문자코드 체계 */
		int totalWord	= iWord - BASE_CODE;
		int iFirstWord	= totalWord / 28 / 21;	// 초성
		int iMiddleWord = totalWord / 28 % 21;	// 중성
		int iLastWord	= totalWord % 28;		// 종성

		mPhonemez[0] = iFirstWord; // 초성저장

		/* 종성이 없는 경우 */
		if(iLastWord == 0) {
			ingWord = SOUND_TABLE[mPhonemez[0]];
			return MixWordStatus.HS_FIRST_V;
		}

		mPhonemez[1] = iMiddleWord + 19; // 중성저장
		iLastWord += 40;

		for(int i = 0; i < 13; i++) {
			if(iLastWord == DIVIDE_JONG_CONSON[i][2]) {
				ingWord = combineHangle(3);
				mPhonemez[2] = DIVIDE_JONG_CONSON[i][0]; // 종성저장
				return MixWordStatus.HS_END_EXCEPTION;
			}
		}

		ingWord = combineHangle(1);
		return MixWordStatus.HS_MIDDLE_STATE;
	}	
}